看完今天的文章,自己試著在專案寫動態寫法後,讀者們會發現今天講的東西很實用。我們可以透過動態的寫法,省去相當多程式碼。
define_method 是目前最常提到的用法,該用法可以動態的宣告方法
class Person 
  define_method :greeting, -> { puts 'Hello!' }
end
Person.new.greeting # => Hello!
上面的例子中,greeting 等同於以下寫法。
class Person 
  def greeting
   puts 'Hello!' 
  end
end
⭐️ 接著我們來說明我在寄信服務上的動態方法
class NotificationMailer < ApplicationMailer
  # 所有方法
  MAIL_METHODS = %w[
    order_init order_need_pay order_canceled_by_customer
    order_canceled_by_user order_canceled_not_pay
    order_shipped order_arrived order_ship_failed
    invoice_issued return_applied return_failed return_partial_failed return_done
    maintain_quoted register member_upgrade member_renewal expiring_accumulate
    member_expiring cart acquire_rebate expiring_credit_point maintain_back
  ]
  MAIL_METHODS.each do |key|
    define_method(key) do |email, instance, title = nil|
      @instance = instance
      mail(to: email, subject: title || send("#{key}_title"))
    end
  end
end
上面為mailer的方法,由於不同的寄信方式內容大致上相同,就可以使用define_method。其中 define_method的email, instance, title = nil,都是被帶入的參數。上述的方式也可以用method_missing實現,method_missing在後面會介紹到,不過這裡用define_method會比較直觀。
在我剛開始使用send的時候,我問自己一個問題?這個問題是Ruby的方法是Symbol嗎?上網查了以後,發現很多人都有類似的問題。確實symbol在Ruby的世界中佔取的object_id相同,所以也可以想像得到,我們可以使用send 搭配symbol做動態的呼叫方法
# 意思相同
1 + 1 = 2
1.send(:+, 1)
# 意思相同
"We are the world".upcase
"We are the world".send(:.upcase)
# 使用客製化 send
class Klass
  def hello(*args)
    "Hello " + args.join(' ')
  end
end
k = Klass.new
k.send(:hello, "gentle", "readers")   #=> "Hello gentle readers"
send的第一個參數是方法,第二個以後全部都是帶進來的參數。接著我們講send如何搭配其他方法!
send & respond_to? 的搭配簡直是絕妙。以下的寫法A,可以使用寫法B代替
print "Search for: "
request = gets.chomp
# 寫法A
if request == "writer"
  puts book.writer
elsif request == "press"
  puts book.press
elseif request == "date"
  puts book.date
else
  puts "Input error"
end
# 寫法b
if book.respond_to?(request)
  puts book.send(request)
else
  puts "Input error"
end
⭐️ 接著我們看respond_to? 的好處?使用respond_to?被使用以後,就不必擔心會有undefined method的錯誤。
obj = Object.new
if obj.respond_to?("talk")
   obj.talk
else
   puts "Sorry, object can't talk!"
end
# 若找不到talk,也不會跳出undefined method 'talk' for #<Object:0x12345678> (NoMethodError)的錯誤
⭐️ 學習rails的讀者們千萬要記住, respond_to 是Rails 控制器的用法,和 respond_to? 完全是不一樣的概念喔!
def index
  @people = Person.find(:all)
  respond_to do |format|
    format.html
    format.json { render :json => @people.as_json }
  end
end
⭐️ 以下為send 與 define_method 的搭配。
class A
  def create_method(name, &block)
    self.class.send(:define_method, name, &block)
  end
end
a = A.new
a.create_method(:run) { "Running Man" }
a.run
#=> "Running Man"
send和define_method的搭配方法,確實可以玩很多不同的變化 ?,後面介紹的method_missing,就會搭配send和define_method
回傳self在Ruby是很常見的寫法,此外符合設計流程的Builder Pattern
class Salad  
  def initialize    
    @ingredients = []  
  end  
  
  def add_veg   
    @ingredients << :vegatable 
    
    self  
  end 
end
salad = Salad.new      #=> #<Salad:0x00007fc42151a3a0 @ingredients=[]>
salad.add_veg          #=> #<Salad:0x00007fc42151a3a0 @ingredients=[:vegatable]>
salad.instance_eval {@ingredients}  #=> [:vegatable]
salad.add_veg          #=> #<Salad:0x00007fc42151a3a0 @ingredients=[:vegatable, :vegatable]>
salad.instance_eval {@ingredients}  #=> [:vegatable, :vegatable]
class Stack  
  def initialize    
    @store = Array.new  
  end  
  def push(element)    
    @store.push(element)    
    
    self  
  end
  
  def pop
    @store.pop
    
    self
  end
end
stack = Stack.new
#=> #<Stack:0x00007fc42dd78ab8 @store=[]>
stack.push '1'
#=> #<Stack:0x00007fc42dd78ab8 @store=["1"]>
tack.push '1'
#=> #<Stack:0x00007fc42dd78ab8 @store=["1", "1"]>
stack.push '1'
#=> #<Stack:0x00007fc42dd78ab8 @store=["1", "1", "1"]>
stack.pop
#=> #<Stack:0x00007fc42dd78ab8 @store=["1", "1"]>
stack.pop
#=> #<Stack:0x00007fc42dd78ab8 @store=["1"]>
stack.pop
#=> #<Stack:0x00007fc42dd78ab8 @store=[]>
以上面的例子來講,我們做了多次push, pop,實體變數刪改元素。回傳self,可以讓我們可以連鎖使用push, pop的動作,達到改動object的目的
在Ruby當中,這不算是陌生的方法,有時候在報錯的時候都會看到。
除了報錯以外,我們還可以用method_missing做一些好玩的事情:
class Developer
  def method_missing method, *args, &block
    # 若 prefix 不為 it_day ,則繼承父層(預設)的 method_missing ➡️ 報錯
    return super method, *args, &block unless method.to_s =~ /^it_\w+/
    
    self.class.send(:define_method, method) do
      "我要挑戰IT鐵人賽主題為 " + method.to_s.gsub(/^it_/, '').to_s + " 的文章"
    end
    
    self.send method, *args, &block
  end
end
itday = Developer.new
itday.it_rails        #=> "我要挑戰IT鐵人賽主題為 rails 的文章"
itday.it_javascript   #=> "我要挑戰IT鐵人賽主題為 javascript 的文章"
當我們在寫條件句,或者case when的時候,可以想想可以用什麼動態的寫法省去多餘的code。Rails 本身是個遵照慣例的框架,所以熟悉一段時間 Rails之後,不難發現其實很多規律性的地方都可以使用動態生成的寫法來作替代。
接著我們來複習目前已經講了幾個設計流程
從Day18-22,我們會開始講解畫面。